#ifndef _SDIO_HW_H_
#define _SDIO_HW_H_

#include "soc.h"

/**
 * SDIO支持
 *     1. 多媒体卡(MMC),V4.2版本，1/4/8bit，支持1个4.2的卡或多个4.1之前的卡，
 *        仅多媒体卡支持流读写;
 *     2. SD卡，V2.0版本，支持1/4bit，根据容量又分为;
 *         1. SDSC卡，最高容量2GB，文件系统FAT12/16;
 *         2. SDHC卡，最高容量32GB，文件系统FAT32;
 *         3. SDXC卡，最高容量2TB，文件系统exFAT;
 *         4. SDUC卡，最高容量128TB;
 *     3. SD I/O卡(电视卡、wifi等)，v2.0版本，1/4bit，仅SDIO卡支持多字节传输;
 *     4. CE-ATA(硬盘)，V1.1版本;
 *     5. 不支持SPI;
 *
 * 时钟
 *     1. MMC V3.31及之前的版本，0~20MHz;
 *     2. MMC V4.0/4.2，0~48MHz;
 *     3. SD/SD I/O卡，0~25MHz;
 *     4. SDIOCLK由PLLQ分频得到，最大48MHz
 *     5. CPU通过APB2访问SDIO的寄存器及FIFO，APB2 >= 3/8*SDIOCLK
 *
 * 信号
 *     1. MMC V3.31及之前的版本初始化阶段CMD脚需要配置为开漏，其他模式可配
 *         置为推挽;
 *     2. 忙信号，SDIO_D0被从机拉低;
 *     3. 中断信号，SDIO_D1，低电平有效;
 *     3. 命令超时，64个SDIO输出时钟周期;
 *     4. 命令状态机空闲模式最少保持为8个SDIOCLK周期，即新命令和前一个响应结
 *        束的时间间隔;
 *     5. 命令未发送时，CMD引脚处于高阻态，发送时，CMD的数据与SDIOCLK的上升
 *        沿同步;
 *     6. 数据线宽度有1/4/8bit可配，空闲时为高阻态，与SDIOCLK的上升沿同步;
 *     7. 卡检测，目前板卡未连接卡检测引脚，卡插入后会将卡检测引脚拉低;
 *
 * 电源
 *     1. 可使能节电模式，当总线处于空闲模式时（命令和数据单元进入空闲
 *        阶段8个时钟周期后）主机会停止发送时钟;
 *
 * 写保护
 *     1. 卡内部有专用寄存器用于写保护，可以设置密钥，也可以通过格式化来清除
 *        密钥;
 *     2. 机械保护开关，卡上的开关推向接口侧表示写保护打开，该信号仅由主机
 *        检测，和从机无关，目前开发板上并未连接WP检测引脚;
 *
 * 数据
 *     1. 数据通信时有块数据（带有CRC16校验）、流数据（仅MMC）、多字节数据（仅SDIO卡）
 *     2. 命令格式，数据先发送高位
 *     +-------------+---------+------+-----------+
 *     |    说明     |   位置  | 宽度 |     值    |
 *     +-------------+---------+------+-----------+
 *     | 1. 起始位   | [47:47] |  1   |     0     |
 *     | 2. 传输位   | [46:46] |  1   |     1     |
 *     | 3. 命令索引 | [45:40] |  6   | CMD[5:0]  |
 *     | 4. 参数     | [38: 8] |  32  | ARG[31:0] |
 *     | 5. CRC7     | [ 7: 1] |  7   |     -     |
 *     | 6. 结束位   | [ 0: 0] |  1   |     1     |
 *     +-------------+---------+------+-----------+
 *     3. 响应有两种类型，短响应48bit，长响应136bit，R3响应中不包含CRC，
 *        主机应当忽略CRC失败状态。长响应中RESP1[31:0]->[127:96] ...
 *         RESP3[31:1]->[127:1]，RESP3[0]固定为0;
 *     +-------------+---------+------+--------------+
 *     |    说明     |   位置  | 宽度 |      值      |
 *     +-------------+---------+------+--------------+
 *     | 1. 起始位   | [47:47] |  1   |      0       |
 *     | 2. 传输位   | [46:46] |  1   |      0       |
 *     | 3. 命令索引 | [45:40] |  6   | RESPCMD[5:0] |
 *     | 4. 参数     | [39: 8] |  32  |  RESP1[31:0] |
 *     | 5. CRC7     | [ 7: 1] |  7   |      -       |
 *     | 6. 结束位   | [ 0: 0] |  1   |      1       |
 *     +-------------+---------+------+--------------+
 *
 *     +-------------------+-----------+------+--------------+
 *     |       说明        |    位置   | 宽度 |      值      |
 *     +-------------------+-----------+------+--------------+
 *     | 1. 起始位         | [135:135] |  1   |      0       |
 *     | 2. 传输位         | [134:134] |  1   |      0       |
 *     | 3. 保留           | [133:128] |  6   |      3F      |
 *     | 4. CID/CSD包含CRC | [127:  1] | 127  |  RESP1/2/3/4 |
 *     | 5. 结束位         | [  0:  0] |  1   |      1       |
 *     +-------------------+-----------+------+--------------+
 *
 *     4. 用户数据，低字节先发出，也即小端字节序，FIFO中的数据不需要反转
 *        FIFO[31:0]=b3[7:0]b2[7:0]b1[7:0]b0[7:0],FIFO[7:0]和b0都处于低地址
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     | DAT | 起始位 |   第一字节  | ... |    第n字节  | CRC | 结束位 |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     |  0  |   0    | b7 b6 .. b0 |     | b7 b6 .. b0 | CRC |   1    |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     | DAT | 起始位 |   第一字节  | ... |    第n字节  | CRC | 结束位 |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     |  3  |   0    |    b7 b3    |     |     b7 b3   | CRC |   1    |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     |  2  |   0    |    b6 b2    |     |     b6 b2   | CRC |   1    |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     |  1  |   0    |    b5 b1    |     |     b5 b1   | CRC |   1    |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     |  0  |   0    |    b4 b0    |     |     b4 b0   | CRC |   1    |
 *     +-----+--------+-------------+-----+-------------+-----+--------+
 *     5. 寄存器数据，高位先发出，但FIFO中是小端字节序，每个word内需要进行
 *        字节序反转，也即FIFO[31:0]=[487:480][495:488][503:496][511:504]
 *     +-----+--------+--------------------+-----+--------+
 *     | DAT | 起始位 |        ACMD13      | CRC | 结束位 |
 *     +-----+--------+--------------------+-----+--------+
 *     |  0  |   0    | b511 b510 .. b1 b0 | CRC |   1    |
 *     +-----+--------+--------------------+-----+--------+
 *     +-----+--------+--------------------+-----+--------+
 *     | DAT | 起始位 |        ACMD13      | CRC | 结束位 |
 *     +-----+--------+--------------------+-----+--------+
 *     |  3  |   0    | b511 b507 .. b7 b3 | CRC |   1    |
 *     +-----+--------+--------------------+-----+--------+
 *     |  2  |   0    | b510 b506 .. b6 b2 | CRC |   1    |
 *     +-----+--------+--------------------+-----+--------+
 *     |  1  |   0    | b509 b505 .. b5 b1 | CRC |   1    |
 *     +-----+--------+--------------------+-----+--------+
 *     |  0  |   0    | b508 b504 .. b4 b0 | CRC |   1    |
 *     +-----+--------+--------------------+-----+--------+
 */

typedef struct {
	_RW POWER;         /*!< SDIO power control register,    offset: 0x00 */
	_RW CLKCR;         /*!< SDI clock control register,     offset: 0x04 */
	_RW ARG;           /*!< SDIO argument register,         offset: 0x08 */
	_RW CMD;           /*!< SDIO command register,          offset: 0x0C */
	_OR RESPCMD;       /*!< SDIO command response register, offset: 0x10 */
	_OR RESP1;         /*!< SDIO response 1 register,       offset: 0x14 */
	_OR RESP2;         /*!< SDIO response 2 register,       offset: 0x18 */
	_OR RESP3;         /*!< SDIO response 3 register,       offset: 0x1C */
	_OR RESP4;         /*!< SDIO response 4 register,       offset: 0x20 */
	_RW DTIMER;        /*!< SDIO data timer register,       offset: 0x24 */
	_RW DLEN;          /*!< SDIO data length register,      offset: 0x28 */
	_RW DCTRL;         /*!< SDIO data control register,     offset: 0x2C */
	_OR DCOUNT;        /*!< SDIO data counter register,     offset: 0x30 */
	_OR STA;           /*!< SDIO status register,           offset: 0x34 */
	_RW ICR;           /*!< SDIO interrupt clear register,  offset: 0x38 */
	_RW MASK;          /*!< SDIO mask register,             offset: 0x3C */
	_NU RESERVED0[2];  /*!< Reserved, 0x40-0x44                          */
	_OR FIFOCNT;       /*!< SDIO FIFO counter register,     offset: 0x48 */
	_NU RESERVED1[13]; /*!< Reserved, 0x4C-0x7C                          */
	_RW FIFO[32];      /*!< SDIO data FIFO register,        offset: 0x80 */
} SDIO_Struct;

extern SDIO_Struct SOC_SDIO;

/**
 * 电源控制，两次写该寄存器至少间隔7个CPU时钟，写入数据后，在3个SDIOCLK以及
 * 2个APB2周期内无法写入新数据
 */
#define SDIO_POWER_DOWN         (0x0U<<0)  // 掉电，停止为卡提供时钟
#define SDIO_POWER_UP           (0x3U<<0)  // 通电，为卡提供时钟

/**
 * 时钟控制寄存器，MMC/SD/SDIO卡识别时，SDIO输出时钟应小于400KHz。为卡分配
 * 相对地址时可以采用总线的最大频率。等待卡数据时，也可以停止SDIO的时钟输出。
 */
#define SDIO_CLKCR_CLKDIV_MASK  (0xFFU<<0)
#define SDIO_CLKCR_CLKDIV_2_257D(div)  ((div)-2)  // SDIO输出时钟相对于
                                                  // SDIOCLK分频，需要配置
                                                  // SDIO_CLKCR_BYPASS_DIS
#define SDIO_CLKCR_CLKEN_DIS    (0x0U<<8)
#define SDIO_CLKCR_CLKEN_EN     (0x1U<<8)  // 使能SDIO时钟输出
#define SDIO_CLKCR_PWRSAV_DIS   (0x0U<<9)
#define SDIO_CLKCR_PWRSAV_EN    (0x1U<<9)  // 使能节能模式，仅在总线激活时发
                                           // 送SDIO时钟
#define SDIO_CLKCR_BYPASS_DIS   (0x0U<<10) // 使能CLKDIV分频
#define SDIO_CLKCR_BYPASS_EN    (0x1U<<10) // SDIOCLK直接输出，不进行分频
#define SDIO_CLKCR_WIDBUS_MASK  (0x3U<<11)
#define SDIO_CLKCR_WIDBUS_1     (0x0U<<11) // 1bit数据线
#define SDIO_CLKCR_WIDBUS_4     (0x1U<<11) // 4bit数据线
#define SDIO_CLKCR_WIDBUS_8     (0x2U<<11) // 8bit数据线
#define SDIO_CLKCR_NEGEDGE_RISE (0x0U<<13) // 在SDIOCLK上升沿产生SDIO输出
#define SDIO_CLKCR_NEGEDGE_FALL (0x1U<<13) // 在SDIOCLK下降沿产生SDIO输出
#define SDIO_CLKCR_HWFC_DIS     (0x0U<<14)
#define SDIO_CLKCR_HWFC_EN      (0x1U<<14) // 使能硬件流控，该功能用于避免
                                           // FIFO的溢出，当FIFO溢出时，硬件
                                           // 会停止SDIO的时钟输出，冻结状
                                           // 态机，开启硬件流控会影响到SDIO
                                           // TX，会出现发送数据CRC错误，需要
                                           // 将分频寄存器配置大于2，也即4分频
                                           // 后，才能正常工作，关闭硬件流控则
                                           // 分频寄存器可以配置为0，即2分频

/**
 * 命令参数部分
 */
#define SDIO_ARG_0_FFFFFFFFH(arg)   (arg)

/**
 * 命令配置
 */
#define SDIO_CMD_INDEX_0_3FH(idx)  ((idx)<<0) // 命令索引
#define SDIO_CMD_WAITRESP_NONE  (0x0U<<6)  // 无响应
#define SDIO_CMD_WAITRESP_SHORT (0x1U<<6)  // 短响应
#define SDIO_CMD_WAITRESP_LONG  (0x3U<<6)  // 长响应
#define SDIO_CMD_WAITINT_DIS    (0x0U<<8)
#define SDIO_CMD_WAITINT_EN     (0x1U<<8)  // 禁止命令超时，等待中断请求
#define SDIO_CMD_WAITPEND_DIS   (0x0U<<9)  // 立即发送命令
#define SDIO_CMD_WAITPEND_EN    (0x1U<<9)  // 等待数据传输结束后发送命令
#define SDIO_CMD_CPSMEN_DIS     (0x0U<<10)
#define SDIO_CMD_CPSMEN_EN      (0x1U<<10) // 命令状态机使能
#define SDIO_CMD_SUSPEND_DIS    (0x0U<<11)
#define SDIO_CMD_SUSPEND_EN     (0x1U<<11) // 要发送的命令为挂起命令，仅SDIO卡
#define SDIO_CMD_COMPL_DIS      (0x0U<<12)
#define SDIO_CMD_COMPL_EN       (0x1U<<12) // 使能命令完成信号
#define SDIO_CMD_IE_DIS         (0x1U<<13)
#define SDIO_CMD_IE_EN          (0x0U<<13) // 使能CE-ATA设备的中断
#define SDIO_CMD_ATACMD         (0x1U<<14) // 发送ATA CMD61命令

/**
 * 响应中的命令索引值
 */
#define _SDIO_GetResCmd(dev)    ((dev).RESPCMD)

/**
 * 响应内容，对于48bits短响应，响应数据[39:8]存储在RESP1[31:0]中
 */
#define SDIO_RESP

/**
 * 数据定时器，根据SDIO输出时钟计时，当数据状态机进入WaitR或忙状态后开始递减
 * 计数，减到0后将会置起超时标志，数据传输应当先写计时器和长度再写控制寄存器
 */
#define SDIO_DTIMER_0_FFFFFFFFH(val)  (val)

/**
 * 数据长度，对于块传输，数据长度必须是块长度的整数倍，对于SDIO多字节传输，
 * 数据长度在1~512之间。
 */
#define SDIO_DLEN_0_1FFFFFFH(len)     (len)

/**
 * 数据控制
 */
#define SDIO_DCTRL_DTEN_EN         (0x1U<<0)  // 使能数据传输，硬件清零
#define SDIO_DCTRL_DTDIR_OUT       (0x0U<<1)  // 主机发出
#define SDIO_DCTRL_DTDIR_IN        (0x1U<<1)  // 主机输入
#define SDIO_DCTRL_DTMODE_BLOCK    (0x0U<<2)  // 块传输
#define SDIO_DCTRL_DTMODE_STREAM   (0x1U<<2)  // 流传输，SDIOEN_DIS
#define SDIO_DCTRL_DTMODE_BYTES    (0x1U<<2)  // 多字节传输，SDIOEN_EN用于SDIO
                                              // 卡操作
#define SDIO_DCTRL_DMAEN_DIS       (0x0U<<3)
#define SDIO_DCTRL_DMAEN_EN        (0x1U<<3)  // 使能DMA
#define SDIO_DCTRL_DBLOCKSIZE_1B   (0x0U<<4)  // 每块数据大小1字节
#define SDIO_DCTRL_DBLOCKSIZE_2B   (0x1U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_4B   (0x2U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_8B   (0x3U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_16B  (0x4U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_32B  (0x5U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_64B  (0x6U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_128B (0x7U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_256B (0x8U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_512B (0x9U<<4)
#define SDIO_DCTRL_DBLOCKSIZE_1KB  (0xAU<<4)
#define SDIO_DCTRL_DBLOCKSIZE_2KB  (0xBU<<4)
#define SDIO_DCTRL_DBLOCKSIZE_4KB  (0xCU<<4)
#define SDIO_DCTRL_DBLOCKSIZE_8KB  (0xDU<<4)
#define SDIO_DCTRL_DBLOCKSIZE_16KB (0xEU<<4)
#define SDIO_DCTRL_DBLOCKSIZE(val) ((val)<<4)
#define SDIO_DCTRL_RW_DIS          (0x0U<<8)
#define SDIO_DCTRL_RW_ACTIVE       (0x1U<<8)  // 激活读取等待状态
#define SDIO_DCTRL_RW_STOP         (0x3U<<8)  // 读取等待状态停止，激活读取
                                              // 等待状态
#define SDIO_DCTRL_RWMOD_D2        (0x0U<<10) // 通过D2进行读取等待控制
#define SDIO_DCTRL_RWMOD_CK        (0x1U<<10) // 通过时钟进行读取等待控制
#define SDIO_DCTRL_SDIOEN_DIS      (0x0U<<11)
#define SDIO_DCTRL_SDIOEN_EN       (0x1U<<11) // 数据状态机用于执行特定于
                                              // SDIO卡的操作

/**
 * 数据状态机处于Wait_R和Wait_S时，DCOUNT加载DLEN的数值并递减，减到0后进入
 * 空闲状态，置起DATAEND标志。仅当数据传输完成后才应当读取该寄存器。
 */
#define _SDIO_GetWaitCount(dev)    ((dev).DCOUNT)

/**
 * 状态标志，STA只读，ICR写1清除，MASK写1使能中断，[21:11]标志是动态变化的，
 * 不需要ICR进行清除，ICR写1后立即读取STA标志仍然存在，是因为总线延迟的原因
 */
#define SDIO_FLAG_CCRCFAIL      (0x1U<<0)  // 已接收命令响应，CRC 校验失败
#define SDIO_FLAG_DCRCFAIL      (0x1U<<1)  // 已发送/接收数据块，CRC 校验失败
#define SDIO_FLAG_CTIMEOUT      (0x1U<<2)  // 命令响应超时，64个SDIO输出时钟
#define SDIO_FLAG_DTIMEOUT      (0x1U<<3)  // 数据超时，n个SDIO输出时钟
#define SDIO_FLAG_TXUNDERR      (0x1U<<4)  // 传输FIFO下溢错误
#define SDIO_FLAG_RXOVERR       (0x1U<<5)  // 接收FIFO上溢错误
#define SDIO_FLAG_CMDREND       (0x1U<<6)  // 已接收命令响应，CRC 校验通过
#define SDIO_FLAG_CMDSENT       (0x1U<<7)  // 命令已发送，不需要响应
#define SDIO_FLAG_DATAEND       (0x1U<<8)  // 数据结束，数据计数器SDIDCOUNT为零
#define SDIO_FLAG_STBITERR      (0x1U<<9)  // 在宽总线模式下，并非在所有数据
                                           // 信号上都检测到了起始位
#define SDIO_FLAG_DBCKEND       (0x1U<<10) // 已发送/接收数据块，CRC校验通过
#define SDIO_FLAG_CMDACT        (0x1U<<11) // 命令传输正在进行中
#define SDIO_FLAG_TXACT         (0x1U<<12) // 数据传输正在进行中
#define SDIO_FLAG_RXACT         (0x1U<<13) // 数据接收正在进行中，当接收FIFO
                                           // 中有数据时，该位也置起
#define SDIO_FLAG_TXFIFOHE      (0x1U<<14) // 传输FIFO半空，至少可以写入
                                           // 8个字到FIFO
#define SDIO_FLAG_RXFIFOHF      (0x1U<<15) // 接收FIFO半满，FIFO中至少有8个字
#define SDIO_FLAG_TXFIFOF       (0x1U<<16) // 传输FIFO已满
#define SDIO_FLAG_RXFIFOF       (0x1U<<17) // 接收FIFO已满，如果使能了硬件流控
                                           // 制，则RXFIFOF信号在FIFO差2个字便
                                           // 变满之前激活
#define SDIO_FLAG_TXFIFOE       (0x1U<<18) // 发送FIFO为空，如果使能了硬件流控
                                           // 制，则TXFIFOE信号在FIFO包含2个
                                           // 字时激活
#define SDIO_FLAG_RXFIFOE       (0x1U<<19) // 接收FIFO为空
#define SDIO_FLAG_TXDAVL        (0x1U<<20) // 传输FIFO中有数据可用
#define SDIO_FLAG_RXDAVL        (0x1U<<21) // 接收FIFO中有数据可用
#define SDIO_FLAG_SDIOIT        (0x1U<<22) // 收到了SDIO中断
#define SDIO_FLAG_CEATAEND      (0x1U<<23) // 针对CMD61收到了CE-ATA命令完成信号
#define SDIO_FLAG_CLEAR_ALL     (0xC007FF) // [23:22] [10:0]
#define SDIO_FLAG_ERRORBITS     (0x23F)    // [9:9] [5:0]

/**
 * FIFO计数器，只读，记录需要从FIFO中读取或写入到FIFO中数据字数，长度不是4字节
 * 整数倍，剩余的字节被视为1个字
 */
#define SDIO_FIFOCNT

/**
 * 32个FIFO寄存器映射到0x80~0xFC地址空间，可以顺序读取或多次从FIFO[0]中读取
 */
#define SDIO_FIFO

#endif /* _SDIO_HW_H_ */
